﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using CIAWorldMap;
using MapSurface;
using MapSurface.LaoMap;
using OSMLib;
using RoadPlot;
using Image = System.Windows.Controls.Image;
using Point = System.Windows.Point;
using Size = System.Windows.Size;

namespace App_Code
{
    /// <summary>
    /// Summary description for MapTiler
    /// </summary>
    public class MapTiler
    {
        static readonly LaosBorder _laosBorder = new LaosBorder();

        public static bool IsTileInLaos(double latTileTop, double lonTileLeft, double latTileBottom, double lonTileEast)
        {
            if (latTileTop < 13.8)
                return false;

            if (latTileBottom > 22.6)
                return false;

            if (lonTileEast < 100.0)
                return false;

            if (lonTileLeft > 107.8)
                return false;

            return true;
        }

        public static void DoOsmPlotPickerMap(Canvas canvas, Size availableSize, 
            double latTileTop, double lonTileLeft, double scale, double latTileBottom, EnumMapLayer enumMapLayer)
        {
            lock (Osm.Ways)
            {
                if (Osm.Ways.Values.Count == 0)
                {
                    var l = new OsmBinLoader(Osm.Ways, Osm.NamedNodes);

                    if (File.Exists(@"c:\osm\osm.bin"))
                    {
                        l.ReadFile(@"c:\osm\osm.bin");
                        _laosBorder.LoadBorder(@"c:\osm\border.bin");
                    }
                    else
                    {
                        l.ReadFile(@"f:\osm\osm.bin");
                        _laosBorder.LoadBorder(@"f:\osm\border.bin");
                    }
                }
            }

            ISurface surface = new Surface(canvas, _laosBorder, availableSize)
                                   {
                                       Left = lonTileLeft,
                                       Top = latTileTop,
                                       XScale = scale*256/360,
                                       YScale = 256/(latTileTop - latTileBottom)
                                   };

            surface.SetLimits();


            if (enumMapLayer == EnumMapLayer.Border)
            {
                ShowFilledCountryBorder(_laosBorder._geoPolygon, canvas, surface);
                return;
            }

            if (enumMapLayer == EnumMapLayer.Background)
            {
                ShowFilledCountry(_laosBorder._geoPolygon, canvas, surface);
                var r = new RenderMapArtifacts(surface);
                r.AddToCanvas();
            }

            List<RoadRoute> routes = GetRoutes(surface, enumMapLayer);
            Road.DrawBorderedRoutes(routes, canvas, IsTraffic(enumMapLayer));

            // long l3 = GC.GetTotalMemory(false);
            // Debug.WriteLine(l3 / 1000000);
            // if (l3 > 200000000)
            //{
            //  GC.Collect();
            //}

            if (enumMapLayer == EnumMapLayer.Traffic)
                return;

            RenderIcons(canvas, scale, surface);
        }

        static bool IsTraffic(EnumMapLayer enumMapLayer)
        {
            return enumMapLayer == EnumMapLayer.Traffic;
        }

        private static List<RoadRoute> GetRoutes(ISurface surface, EnumMapLayer enumMapLayer)
        {
            var routes = new List<RoadRoute>();

            foreach (OsmWay w in Osm.Ways.Values)
            {
                if (surface.WayVisible(w) && w.IsRoad)
                {
                    if (w.IsHighWayVisible(surface.XScale))
                    {
                        bool isProblem = IsTraffic(enumMapLayer) && ((w.GetName.Contains("Road") || (w.GetName == "N13")));

                        if (isProblem || !IsTraffic(enumMapLayer))
                        {
                            double size = w.RoadWidth; // range 1..12

                            if ((size <= 1.0) && (surface.XScale < 50000))
                                // don't show paths and minor roads on big maps
                                continue;

                            LogScale(surface.XScale, ref size);

                            var points = (from OsmNode n1 in w.Nodes
                                          let px = surface.GetPx(n1._wgs.Longitude)
                                          let py = surface.GetPy(n1._wgs.Latitude)
                                          select new Point(px, py)).ToList();

                            double roadWidth = size * 6;

                            var rr = new RoadRoute(points, RoadLegend.RoadLengendFactory(w.GetName, roadWidth * 4), size * 4,
                                                   (size * 3) - 1, roadWidth);
                            routes.Add(rr);
                        }
                    }
                }
            }
            return routes;
        }

        static void ShowFilledCountryBorder(IEnumerable<Coord> geoPolygon, Canvas canvas1, ISurface surface)
        {
            var myLinearGradientBrush = new SolidColorBrush(Color.FromArgb(0, 128, 128, 128));

            canvas1.Children.Clear();

            var polly = new Polyline
            {
                StrokeThickness = 3,
                Fill = myLinearGradientBrush,
                Points = new PointCollection(),
                StrokeStartLineCap = PenLineCap.Round,
                StrokeEndLineCap = PenLineCap.Round,
                StrokeLineJoin = PenLineJoin.Round,
                Stroke = Brushes.DarkRed
            };


            foreach (var n1 in geoPolygon)
            {
                double px = surface.GetPx(n1._lon);
                double py = surface.GetPy(n1._lat);
                polly.Points.Add(new Point(px, py));
            }

            if (polly.Points.Count != 0)
                canvas1.Children.Add(polly);
        }


        static void ShowFilledCountry(IEnumerable<Coord> geoPolygon, Canvas canvas1, ISurface surface)
        {
            var myLinearGradientBrush = new LinearGradientBrush
                                            {
                                                StartPoint = new Point(0, 0),
                                                EndPoint = new Point(0.4, 1)
                                            };
            myLinearGradientBrush.GradientStops.Add(
                new GradientStop(Colors.Blue, 0.0));
            myLinearGradientBrush.GradientStops.Add(
                new GradientStop(Colors.Green, 0.25));
            myLinearGradientBrush.GradientStops.Add(
                new GradientStop(Colors.Orange, 0.75));
            myLinearGradientBrush.GradientStops.Add(
                new GradientStop(Colors.DarkOrange, 1.0));
      
            canvas1.Children.Clear();

            var polly = new Polyline
                            {
                                StrokeThickness = 3,
                                Fill = myLinearGradientBrush,
                                Points = new PointCollection(),
                                StrokeStartLineCap = PenLineCap.Round,
                                StrokeEndLineCap = PenLineCap.Round,
                                StrokeLineJoin = PenLineJoin.Round,
                                Stroke = Brushes.DarkRed
                            };


            foreach (var n1 in geoPolygon)
            {
                double px = surface.GetPx(n1._lon);
                double py = surface.GetPy(n1._lat);
                polly.Points.Add(new Point(px, py));
            }

            if (polly.Points.Count != 0)
                canvas1.Children.Add(polly);
        }

        private static void RenderIcons(Canvas canvas, double scale, ISurface surface)
        {
            var l = (int)Math.Log(scale, 2);

            if (l < 14)
                return;

            var pngScale = 1; //= (int)(scale / Math.Pow(2, 14));
            switch (l)
            {
                case 14:
                    pngScale = 4;
                    break;

                case 15:
                    pngScale = 4;
                    break;

                case 16:
                    pngScale = 4;
                    break;

                case 17:
                    pngScale = 8;
                    break;

                case 18:
                    pngScale = 16;
                    break;

                case 19:
                    pngScale = 32;
                    break;

                case 20:
                    pngScale = 64;
                    break;

                case 21:
                    pngScale = 128;
                    break;
            }


            PlaceResturants(pngScale, surface, canvas);
            PlaceTemples(pngScale, surface, canvas);
        }

        private static void PlaceResturants(int pngScale, ISurface surface, Canvas canvas)
        {
            string imageFileName = IconRepository.Restaurant(pngScale);
            if (imageFileName == null)
                return;

            var src = new BitmapImage();
            src.BeginInit();
            src.UriSource = new Uri(imageFileName, UriKind.RelativeOrAbsolute);
            src.CacheOption = BitmapCacheOption.OnLoad;
            src.EndInit();
            // source image DPI must be 96dpi - otherwise WPF become irritating and doesn't use pixel size!

            foreach (OsmNode n in Osm.NamedNodes)
            {
                if (n.IsRestaurant)
                {
                    PlaceBuildingBlobNode(n, canvas, src, surface, pngScale);
                }
            }
        }

        private static void PlaceTemples(int pngScale, ISurface surface, Canvas canvas)
        {
            string imageFileName = IconRepository.Temple(pngScale);
            if (imageFileName == null)
                return;

            var src = new BitmapImage();
            src.BeginInit();
            src.UriSource = new Uri(imageFileName, UriKind.RelativeOrAbsolute);
            src.CacheOption = BitmapCacheOption.OnLoad;
            src.EndInit();
            // source image DPI must be 96dpi - otherwise WPF become irritating and doesn't use pixel size!

            foreach (OsmNode n in Osm.NamedNodes)
            {
                if (n.IsTemple)
                {
                    PlaceBuildingBlobNode(n, canvas, src, surface, pngScale);
                }
            }
        }

        private static void PlaceBuildingBlobNode(OsmNode osmNode, Canvas canvas, BitmapImage src, ISurface surface, int iconWidth)
        {
            if (osmNode.IsPlace)
                return;

            double px = surface.GetPx(osmNode._wgs.Longitude);
            double py = surface.GetPy(osmNode._wgs.Latitude);

            if ((py + (iconWidth * 2)) < 0)
                return;

            // allow text to the left of the icon
            if ((px + (iconWidth * 10)) < 0)
                return;

            // allow room for legend beneath icon
            if ((py - (iconWidth * 2.0)) > (surface.Height() * 1.08))
                return;

            // allow text to the right of the icon

            if ((px - (iconWidth * 10)) > surface.Width())
                return;

            if (iconWidth > 4)
            {
                string legend = osmNode.Name;
                if (legend != null)
                {
                    var t = new TextBlock
                                {
                                    FontSize = iconWidth/1.6,
                                    FontFamily = new FontFamily("Verdana"),
                                    Text = legend,
                                    HorizontalAlignment = HorizontalAlignment.Center,
                                };

                    t.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));

                    var b = new Border
                                {
                                    CornerRadius = new CornerRadius(iconWidth/3.2),
                                    Padding = new Thickness(iconWidth/6.4),
                                    Background = new SolidColorBrush(Color.FromArgb(128, 255, 255, 255)),
                                    Width = t.DesiredSize.Width + (iconWidth/1.6) + 20,
                                    BorderThickness = new Thickness(2),
                                    BorderBrush = new SolidColorBrush(Color.FromArgb(255, 100, 100, 100))
                                };

                    canvas.Children.Add(b);
                    b.Child = t;

                    Canvas.SetLeft(b, px - b.Width/2.0);
                    Canvas.SetTop(b, py + (iconWidth/6.0));
                }
            }

            var iconImage = new Image { Source = src };

            RenderOptions.SetBitmapScalingMode(iconImage, BitmapScalingMode.NearestNeighbor);

            Canvas.SetLeft(iconImage, px - iconWidth / 2.0);
            Canvas.SetTop(iconImage, py - iconWidth / 2.0);
            canvas.Children.Add(iconImage);

            /*
            // Rectangle r = new Rectangle();
            r.Fill = Brushes.Blue;
            r.StrokeThickness = 1;
            r.Stroke = Brushes.Black;
            r. Height = pngScale;
            r.Width = pngScale;

            Canvas.SetLeft(r, px - pngScale / 2.0);
            Canvas.SetTop(r, py - pngScale / 2.0);
            canvas.Children.Add(r);
                * */

            // el.Stroke = Brushes.Black;
            
        }

        private static void LogScale(double scale, ref double size)
        {
            if (scale < 50)
            {
                size *= 0.05 / 6.0;
                return;
            }

            if (scale < 100)
            {
                size *= 0.1 / 6.0;
                return;
            }

            if (scale < 200)
            {
                size *= 0.15 / 6.0;
                return;
            }

            if (scale < 500)
            {
                size *= 0.2 / 6.0;
                return;
            }

            if (scale < 1000)
            {
                size *= 0.3 / 6.0;
                return;
            }

            if (scale < 2000)
            {
                size *= 0.4 / 6.0;
                return;
            }

            if (scale < 5000)
            {
                size *= 0.5 / 6.0;
                return;
            }

            if (scale < 10000)
            {
                size *= 0.5 / 6.0;
                return;
            }

            if (scale < 20000)
            {
                size *= 1.0 / 6.0;
                return;
            }

            if (scale < 50000)
            {
                size *= 2.0 / 6.0;
                return;
            }

            if (scale < 100000)
            {
                size *= 4.0 / 6.0;
                return;
            }

            if (scale < 200000)
            {
                size *= 5.0 / 6.0;
                return;
            }
        }
    }
}